﻿using System;
using System.Text.RegularExpressions;
using JetBrains.Annotations;



namespace Fine.Utils.Functions
{
    /// <summary>
    /// String extensions — a set of useful common functions of string.
    /// </summary>
    public static class StringExt
    {

        /// <summary>
        /// Normalizes the string: makes it with no trailing spaces and turns into null if empty.
        /// </summary>
        /// <param name="str">string to normalize.</param>
        /// <returns>trimmed non-empty string or null.</returns>
        [CanBeNull]
        public static string TrimAndNull ([CanBeNull] this string str)
        {
            if (str == null)
                return null;

            str = str.Trim();
            return str.Length > 0 ? str : null;
        }


        /// <summary>
        /// Normalizes the string: makes it with no trailing spaces.
        /// Nulls are turned into empty strings.
        /// </summary>
        /// <param name="str">string to normalize.</param>
        /// <returns>trimmed or an empty string.</returns>
        [NotNull]
        public static string TrimAndEmpty ([CanBeNull] this string str)
        {
            return str != null ? str.Trim() : string.Empty;
        }


        /// <summary>
        /// Checks whether the string is empty or null. 
        /// Empty string means the string length is 0; 
        /// a string with characters (even spaces) threated as non-empty one.
        /// </summary>
        /// <param name="str">string to test.</param>
        /// <returns>is empty.</returns>
        public static bool IsEmpty ([CanBeNull] this string str)
        {
            // ReSharper disable ReplaceWithStringIsNullOrEmpty
            return str == null || str.Length == 0;
        }


        /// <summary>
        /// Checks whether the string is not empty and is not null. 
        /// Empty string means the string length is 0; 
        /// a string with characters (even spaces) threated as non-empty one.
        /// </summary>
        /// <param name="str">string to test.</param>
        /// <returns>is not empty.</returns>
        public static bool IsNotEmpty ([CanBeNull] this string str)
        {
            // ReSharper disable ReplaceWithStringIsNullOrEmpty
            return str != null && str.Length > 0;
        }


        /// <summary>
        /// Counts how many spaces before the first non-space character (are at the begin of the given string).
        /// In case of an empty (or null) string or a string contains spaces only
        /// it returns a biggest signed integer.
        /// </summary>
        /// <param name="str">the string where to count heading spaces. Nulls are allowed.</param>
        /// <returns>count of spaces at the begin of the given string, 
        ///          or <see cref="int.MaxValue"/> in case of no non-space characters.</returns>
        public static int Offset ([CanBeNull] this string str)
        {
            if (str != null)
                for (int i = 0,n = str.Length; i < n; i++)
                    if (!char.IsWhiteSpace(str[i]))
                        return i;

            return int.MaxValue;
        }


        [CanBeNull]
        public static string SafeTrimEnd([CanBeNull] this string str)
        {
            return str == null ? null : str.TrimEnd();
        }


        [CanBeNull]
        public static string SafeSubstring([CanBeNull] this string str, int startIndex)
        {
            if (str == null)
                return null;

            if (str.Length > startIndex)
                return str.Substring(startIndex);
            else
                return string.Empty;
        }


        [CanBeNull]
        public static string SafeSubstring([CanBeNull] this string str, int startIndex, int length)
        {
            if (str == null)
                return null;

            int limit = str.Length - startIndex;
            if (limit > 0)
                return str.Substring(startIndex, Math.Min(length, limit));
            else
                return string.Empty;
        }



        [CanBeNull]
        public static Match Matching([CanBeNull] this string str, [NotNull] Regex pattern)
        {
            if (str == null)
                return null;

            var match = pattern.Match(str);
            return match.Success ? match : null;
        }


        [CanBeNull]
        public static string TruncateRight([CanBeNull] this string str, int count)
        {
            if (str == null)
                return null;

            int n = str.Length;
            return count < n ? str.Substring(0, n - count) : string.Empty;
        }


        [CanBeNull]
        public static string AppendIfNo([CanBeNull] this string str, [NotNull] string suffix)
        {
            return str == null 
                ? null 
                : str.EndsWith(suffix) 
                    ? str 
                    : str + suffix;
        }


        [NotNull]
        [StringFormatMethod("template")]
        public static string With(this string template, params object[] args)
        {
            return string.Format(template, args);
        }


        [CanBeNull]
        public static string Enquote1([CanBeNull] this string str)
        {
            if (str == null) return null;
            return '\'' + str + '\'';
        }


        [CanBeNull]
        public static string Enquote2([CanBeNull] this string str)
        {
            if (str == null) return null;
            return '"' + str + '"';
        }


        /// <summary>
        /// Gets a substring-field, delimited with the specified delimiter.
        /// </summary>
        /// <param name="str">string to peek a field from.</param>
        /// <param name="delimiter">delimiter.</param>
        /// <param name="nr">number of field, starting with 1.</param>
        /// <param name="trim">trim the result before return it.</param>
        /// <returns>the field.</returns>
        public static string Field(this string str, char delimiter, int nr, bool trim = false)
        {
            if (str == null)
                return null;
            if (nr <= 0)
                throw new ArgumentException("Illegal number of field: " + nr);

            int n = str.Length;
            int p1 = 0, 
                p2 = n;
            bool found = nr <= 1;
            for (int i = 0,k = 1; i < n; i++)
            {
                if (str[i] == delimiter)
                {
                    k++;
                    if (k == nr) { p1 = i + 1; found = true; }
                    if (k == nr + 1) { p2 = i; break; }
                }
            }

            if (!found) return string.Empty;

            string field = str.Substring(p1, p2 - p1);
            return trim ? field.Trim() : field;
        }

    }
}
